#!/usr/bin/env python
# encoding: utf-8


"""
@file: bianjiaozhuanhuan.py
@time: 2016/11/29 下午7:07
"""
from functools import reduce

# 边角关系转换
# 所有的边角关系的转换，最终都返回一个BaseEqs, 就是推出的可能等式
from mathsolver.functions.base import *
from sympy import (sin, cos, symbols, cancel, fraction, degree, factor, expand, expand_trig, trigsimp, pi, tan,
                   FiniteSet, cot, solveset)
from mathsolver.functions.budengshi import common_opers as co
from itertools import combinations, permutations
from mathsolver.functions.sanjiao import sanjiao_utils as su

a, b, c = symbols('a, b, c')  # 边
SIDES = FiniteSet(a, b, c)
A, B, C = symbols('A, B, C')  # 角
ANGLES = FiniteSet(A, B, C)
# r_symbol = Symbol('r', positive=True)
r_symbol = sympify('r')
angle_pers = map(lambda per: (tuple(per), list(ANGLES - per)[0]),
                 map(lambda per: FiniteSet(*per), permutations(ANGLES, 2)))  # 所有三个角同时出现的情况


def _trig_angle_pers():
    trig_angle_pers = []
    for two_angle, one_angle in angle_pers:
        add_angle = two_angle[0] + two_angle[1]
        sub_angle = two_angle[0] - two_angle[1]
        trig_angle_pers.append((add_angle, one_angle))
        trig_angle_pers.append((sub_angle, one_angle))
    trig_f_combins = co.combin_iters((sin, cos), (sin, cos))
    all_pers = []
    for angle_per in trig_angle_pers:
        for t1, t2 in trig_f_combins:
            all_pers.append((t1(angle_per[0]), t2(angle_per[1])))
    return all_pers


TRIG_ANGLE_PERS = _trig_angle_pers()


# 三角形A,B,C的消元
# 022.当角C与A-B同时出现
# 利用A = pi - (B + C), B = pi - (A + C), C = pi - (A + B)
class XiaoYuan(BaseFunction):
    def solver(self, *args):
        eq = args[0]
        l_expr, r_expr = eq.sympify()
        f = l_expr - r_expr
        if f != cancel(f):
            self.steps.append(['变换可得:', BaseEq([f, cancel(f)]).printing()])
        f, _ = fraction(cancel(f))
        if not _.is_real:
            self.steps.append(['因为', new_latex(_) + '!=0'])
            self.steps.append(['所以', BaseEq([f, 0]).printing()])
        for t1, t2 in TRIG_ANGLE_PERS:
            if (f.coeff(t1) and f.coeff(t2)) or (f.coeff(expand_trig(t1)) and f.coeff(t2)):
                t2_symb = list(t2.free_symbols)[0]
                t2_sub = pi - reduce(lambda _1, _2: _1 + _2, t1.free_symbols)
                f = f.subs(t2_symb, t2_sub)
        sim_f = trigsimp(f)
        if f != sim_f:
            self.steps.append(['化简得:', BaseEq([f, sim_f]).printing()])
        f = sim_f
        self.output.append(BaseEq([f, '0']))
        return self


def angle_elimination(eq):
    """
    角度消元 A+B+C=pi; A+c=2*B
    :param eq:
    :return:
    """
    subs = (('A', 'pi -(B+C)'), ('B', 'pi-(A+C)'), ('C', 'pi-(A+B)'))
    eq_l, eq_r = eq.sympify()
    eq_f = eq_l - eq_r
    symbs = list(eq_f.free_symbols)
    # if not FiniteSet(*symbs).is_subset(ANGLES):
    if not su.i_is_subset(symbs, ANGLES):
        return eq
    if str(eq_f).find('sin') >= 0 or str(eq_f).find('cos') >= 0 or str(eq_f).find('tan') >= 0 or str(eq_f).find(
            'cot') >= 0:
        return eq
    for expr1, expr2 in subs:
        tmp_eq_f = eq_f.subs(expr1, expr2)
        if solve(tmp_eq_f)[0].is_real:
            v = solve(tmp_eq_f)[0]
            symb = list(tmp_eq_f.free_symbols)[0]
            cos_v = cos(v)
            return BaseEq([cos(symb), cos_v])
    return eq


# 角关系化为边关系
# 角化边(只含有角的关系)
# 2cosAsinB=sinC; sinA-sinAcosC=cosAsinC; sin^{2}A+sin^{2}B=sin^{2}C; a-ccosB=b-ccosA
# 不管式子如何复杂，总能化成最直接形式
class JiaoHuaBian(BaseFunction):
    @staticmethod
    def _validate_sides_eq(eq):  # 验证边的关系是否正确
        tmp_a, tmp_b, tmp_c = symbols('a, b, c', positive=True)
        symbs_subs = ((a, tmp_a), (b, tmp_b), (c, tmp_c))
        l_expr, r_expr = eq.sympify()
        tmp_f = l_expr - r_expr
        if degree(tmp_f) > 1:  # 如果是二次的则是无法确定是否正确的
            return True
        tmp_symbs = list(tmp_f.free_symbols)
        result_symb = tmp_symbs[0]
        result_symb_v = list(solveset(tmp_f, symbol=result_symb))[0]
        result_symb_v = result_symb_v.subs(symbs_subs)
        try:
            if result_symb_v < 0:
                return False
        except Exception:  # 无法判定正负的
            return True
        return True

    def solver(self, *args):
        eq = args[0]
        cot_subs = ((cot(A), cos(A) / sin(A)), (cot(B), cos(B) / sin(B)), (cot(C), cos(C) / sin(C)))
        tan_subs = ((tan(A), sin(A) / cos(A)), (tan(B), sin(B) / cos(B)), (tan(C), sin(C) / cos(C)))
        sin_subs = ((sin(A), a / (2 * r_symbol)), (sin(B), b / (2 * r_symbol)), (sin(C), c / (2 * r_symbol)))
        cos_subs = (
            (cos(A), (b ** 2 + c ** 2 - a ** 2) / (2 * b * c)),
            (cos(B), (a ** 2 + c ** 2 - b ** 2) / (2 * a * c)),
            (cos(C), (a ** 2 + b ** 2 - c ** 2) / (2 * a * b))
        )
        l_expr, r_expr = eq.sympify()
        f = l_expr - r_expr
        if f != f.subs(cot_subs):
            self.steps.append(['替换得' + new_latex(f), '=' + new_latex(f.subs(cot_subs))])
        f = f.subs(cot_subs)
        if f != f.subs(tan_subs):
            self.steps.append(['替换得' + new_latex(f), '=' + new_latex(f.subs(tan_subs))])
        f = f.subs(tan_subs)  # 替换掉tan
        eq = BaseEq([f, '0'])
        # eq = XiaoYuan(verbose=True).solver(eq).output[0]  # 消元
        _solver = XiaoYuan(verbose=True).solver(eq)
        eq = _solver.output[0]  # 消元
        self.steps.extend(_solver.steps)
        eq = angle_elimination(eq)
        l_expr, r_expr = eq.sympify()
        f = l_expr - r_expr
        if f != cancel(f):
            self.steps.append(['变换得:', BaseEq([f, cancel(f)]).printing()])
        f, _ = fraction(cancel(f))
        if not _.is_real:
            self.steps.append(['得到:', new_latex(f / _) + '=0'])
            self.steps.append(['因为:', new_latex(_) + '!=0'])
            self.steps.append(['所以:', BaseEq([f, '0']).printing()])
        f = expand_trig(f)
        self.steps.append(['由正弦余弦定理得:', new_latex(f) + '=' + new_latex(f.subs(sin_subs).subs(cos_subs))])
        f = f.subs(sin_subs).subs(cos_subs)
        if f != cancel(f):
            self.steps.append(['变换得:', BaseEq([f, cancel(f)]).printing()])
        f, _ = fraction(cancel(f))
        if not _.is_real:
            self.steps.append(['化简得到:', new_latex(f / _) + '=0'])
            self.steps.append(['因为:', new_latex(_) + '!=0'])
            self.steps.append(['所以:', BaseEq([f, '0']).printing()])
        self.steps.append(['角化边得', ''])
        if f != factor(f):
            self.steps.append(['所以:' + new_latex(f), '=' + new_latex(factor(f))])
            f = factor(f)
            eqs = []
            for arg in f.args:
                symbs = list(FiniteSet(*arg.free_symbols))  # 是否是关系表达式
                # if symbs and symbs.is_subset(SIDES) and JiaoHuaBian._validate_sides_eq(BaseEq([arg, '0'])):
                from mathsolver.functions.sanjiao import sanjiao_utils as sut
                if symbs and sut.i_is_subset(symbs, SIDES) and JiaoHuaBian._validate_sides_eq(BaseEq([arg, '0'])):
                    if len(symbs) == 1:  # 如果只含有一个边的变量
                        symb_values = co.isolve_eq2(BaseEq([arg, 0]), symbs[0])
                        if len(symb_values) == 1 and symb_values[0] == 0:  # 如果形如 m*b*(a + b - 3)
                            self.steps.append(['因为:' + new_latex(symbs[0]), '!=0'])
                        else:
                            eqs.append([arg, 0])
                    else:
                        self.steps.append(['所以', BaseEq([arg, 0]).printing()])
                        eqs.append([arg, 0])
                elif r_symbol in list(symbs):
                    eqs.append([arg, 0])
                    self.steps.append(['所以', BaseEq([arg, 0]).printing()])
            self.output.append(BaseEqs(eqs))
            self.steps.append(['得到:', BaseEqs(eqs).printing()])
            return self
        else:
            self.output.append(BaseEqs([[f, '0'], ]))
            self.steps.append(['得到:', BaseEq([f, '0']).printing()])
        return self


TRIG_EQS = (
    ((b ** 2 + c ** 2 - a ** 2) / (2 * b * c), cos(A)),
    ((a ** 2 + c ** 2 - b ** 2) / (2 * a * c), cos(B)),
    ((a ** 2 + b ** 2 - c ** 2) / (2 * a * b), cos(C))
)
TRIG_SUB_LIST = [list(combinations(TRIG_EQS, i)) for i in range(1, len(TRIG_EQS) + 1)]


def _trig_subs_combins():
    subs_combins = []
    for try_subs in TRIG_SUB_LIST:
        for com_bin in try_subs:
            l_f, r_f = sympify('0'), sympify('0')
            for l_expr, r_expr in com_bin:
                l_f += l_expr
                r_f += r_expr
            subs_combins.append((l_f, r_f))
    return subs_combins


TRIG_SUBS_COMBINS = _trig_subs_combins()


# 边化角(只含有角)
# 2b=a+c
class BianHuaJiao(BaseFunction):
    @staticmethod
    def try_trig_sub(f):  # 尝试变换
        f = cancel(f)
        # eqs_size = len(TRIG_EQS)
        # subs_list = [list(combinations(TRIG_EQS, i)) for i in range(1, eqs_size + 1)]
        # subs_combins = []
        # for try_subs in subs_list:
        #     for com_bin in try_subs:
        #         l_f, r_f = sympify('0'), sympify('0')
        #         for l_expr, r_expr in com_bin:
        #             l_f += l_expr
        #             r_f += r_expr
        #         subs_combins.append((l_f, r_f))
        for l_expr, r_expr in TRIG_SUBS_COMBINS:
            tmp_f = f - l_expr
            tmp_f = cancel(tmp_f)
            mo, de = fraction(tmp_f)
            mo = expand(mo)
            tmp_f = mo / de
            symbs = tmp_f.free_symbols
            if a not in symbs and b not in symbs and c not in symbs:
                f = tmp_f + r_expr
                break
        return f

    def solver(self, *args):
        eq = args[0]
        eq = XiaoYuan(verbose=True).solver(eq).output[0]  # 消元
        cot_subs = (('cot(A)', 'cos(A) / sin(A)'), ('cot(B)', 'cos(B) / sin(B)'), ('cot(C)', 'cos(C) / sin(C)'))
        tan_subs = (('tan(A)', 'sin(A) / cos(A)'), ('tan(B)', 'sin(B) / cos(B)'), ('tan(C)', 'sin(C) / cos(C)'))
        abc_subs = (('a', sin(A) * (2 * r_symbol)), ('b', sin(B) * (2 * r_symbol)), ('c', sin(C) * (2 * r_symbol)))
        l_expr, r_expr = eq.sympify()
        # f = l_expr - r_expr
        f = expand_trig(l_expr - r_expr)
        if f != f.subs(cot_subs).subs(tan_subs):
            self.steps.append(['替换得:', new_latex(f) + '=' + new_latex(f.subs(cot_subs).subs(tan_subs))])
        f = f.subs(cot_subs).subs(tan_subs)
        if f != f.subs(abc_subs):
            self.steps.append(['由正弦定理得:', new_latex(f) + '=' + new_latex(f.subs(abc_subs))])
        f = f.subs(abc_subs)
        f = BianHuaJiao.try_trig_sub(f)
        if f != cancel(f):
            self.steps.append(['变换得到' + new_latex(f), '=' + new_latex(cancel(f))])
        f, _ = fraction(cancel(f))
        if not _.is_real:
            self.steps.append(['因为:' + new_latex(_), '!= 0'])
            self.steps.append(['所以:' + BaseEq([f, 0]).printing(), ''])
        mons = map(lambda _: _[0] * _[1], f.as_coefficients_dict().items())
        coefs_gcd = co.i_gcd(*mons)
        f = factor(expand(f / coefs_gcd))
        f, _ = fraction(f)
        self.steps.append(['所以:' + BaseEq([f, 0]).printing(), ''])
        self.steps.append(['边化角得', BaseEq([f, '0']).printing()])
        self.output.append(BaseEq([f, '0']))
        return self


# 二倍角消元
# 007.关于角a与2a的消元
# sinC+sin(B-A)=sin2A,
class ErBeiJiaoXiaoYuan(BaseFunction):
    @staticmethod
    def valid_sub(f, sub):
        f = f.subs(*sub)
        if str(f).find('zoo') >= 0:
            return False
        return True

    @staticmethod
    def try_cos_2_subs(f, cos_2_subs):  # 返回 f, 或者推出的eqs
        eqs = []
        for cos_2_sub in cos_2_subs:
            tmp_f = expand(f.subs(cos_2_sub))
            if tmp_f != factor(tmp_f):
                tmp_f = factor(tmp_f)
                for arg in tmp_f.args:
                    if not arg.is_real:
                        eqs.append([arg, 0])
                return BaseEqs(eqs)
        return f

    @staticmethod
    def try_angle_subs(f):  # 三角变换的
        angle_subs = ((A, pi - (B + C)), (B, pi - (A + C)), (C, pi - (A + B)))
        for angle_sub in angle_subs:
            tmp_f = expand_trig(f.subs(*angle_sub))
            if len(co.coefficients_dict(tmp_f).keys()) <= 2:
                if tmp_f != factor(tmp_f):
                    tmp_f = factor(tmp_f)
                    eqs = []
                    for arg in tmp_f.args:
                        if not arg.is_real:
                            eqs.append([arg, 0])
                    return BaseEqs(eqs)
                else:
                    return tmp_f
        return f

    def solver(self, *args):
        eq = args[0]
        tan_subs = ((tan(A), sin(A) / cos(A)), (tan(B), sin(B) / cos(B)), (tan(C), sin(C) / cos(C)))
        sin_2_subs = map(lambda s: (sin(2 * s), 2 * sin(s) * cos(s)), (A, B, C))
        cos_2_subs1 = map(lambda s: (cos(2 * s), 2 * cos(s) ** 2 - 1), (A, B, C))
        cos_2_subs2 = map(lambda s: (cos(2 * s), 1 - 2 * sin(s) ** 2), (A, B, C))
        cos_2_subs = zip(cos_2_subs1, cos_2_subs2)
        cos_2_subs_combins = co.combin_iters(*cos_2_subs)
        l_expr, r_expr = eq.sympify()
        src_f = l_expr - r_expr
        f = cancel(l_expr - r_expr)
        f, _ = fraction(f)
        f = f.subs(tan_subs).subs(sin_2_subs)
        result = ErBeiJiaoXiaoYuan.try_cos_2_subs(f, cos_2_subs_combins)
        if isinstance(result, BaseEqs):
            eqs_list = result.sympify()
            eqs = []
            for expr, expr_sub in eqs_list:
                if ErBeiJiaoXiaoYuan.valid_sub(src_f, (expr, expr_sub)):
                    eqs.append([expr, expr_sub])
            self.output.append(BaseEqs(eqs))
            return self
        f = expand_trig(f)
        symbs = FiniteSet(*f.free_symbols)
        if symbs == FiniteSet(A, B, C):
            result = ErBeiJiaoXiaoYuan.try_angle_subs(f)
            if isinstance(result, BaseEqs):
                eqs_list = result.sympify()
                eqs = []
                for expr, expr_sub in eqs_list:
                    if ErBeiJiaoXiaoYuan.valid_sub(src_f, (expr, expr_sub)):
                        eqs.append([expr, expr_sub])
                self.output.append(BaseEqs(eqs))
                return self
            else:
                f = result
        self.output.append(BaseEqs([[f, '0'], ]))
        return self


# 边角关系互换
# Input:只有边的关系/只有角的关系/边角关系都包含(BaseEq)
# Output: 边Side的关系/角Angle的关系
class BianJiaoBianHuan(BaseFunction):
    CLS = [JiaoHuaBian, ]

    def solver(self, *args):
        r = None
        for cl in BianJiaoBianHuan.CLS:
            try:
                r = cl(verbose=True).solver(*args)
                break
            except Exception:
                pass
        if not r:
            raise 'try fail'
        return r


if __name__ == '__main__':
    pass
